package gamesession

import (
	"errors"              // 用于定义匹配过程中的错误（如玩家已在队列中）
	"gameServer/eventbus" // 事件总线，用于发布匹配相关事件（如匹配开始、取消）
	"gameServer/log"      // 日志包，记录匹配过程的关键操作（如玩家加入队列、匹配成功）
	"sort"                // 用于根据匹配策略对玩家队列进行排序（如按技能、地区）
	"sync"                // 提供互斥锁，确保匹配队列在并发操作下的安全
	"time"                // 用于记录匹配请求的创建时间、控制匹配循环间隔
	"github.com/google/uuid" // 生成唯一会话ID
)

/*
本文件实现了游戏匹配组件（Matcher），负责管理玩家的匹配请求、根据指定策略（随机、技能、地区）进行玩家匹配，
并将匹配成功的玩家分配到游戏会话中，是多玩家游戏中“玩家组队”的核心模块。
*/

// MatchmakingStrategy 匹配策略枚举，定义了不同的玩家匹配规则
type MatchmakingStrategy int

// 匹配策略常量，覆盖常见的游戏匹配场景
const (
	StrategyRandom      MatchmakingStrategy = iota // 0: 随机匹配（不考虑玩家属性，随机组队）
	StrategySkillBased                              // 1: 基于技能匹配（将技能水平接近的玩家分到一组）
	StrategyRegionBased                             // 2: 基于地区匹配（将同一地区的玩家分到一组）
)

// MatchRequest 匹配请求结构体，存储玩家发起匹配时的关键信息
type MatchRequest struct {
	PlayerID    string    // 玩家唯一标识（如"player_123"）
	SkillRating int       // 玩家技能等级（用于StrategySkillBased策略）
	Region      string    // 玩家所在地区（用于StrategyRegionBased策略，如"cn"、"us"）
	CreateAt    time.Time // 匹配请求创建时间（用于计算匹配等待时长）
}

// Matcher 匹配器核心结构体，负责管理匹配队列、执行匹配逻辑、与会话管理器交互
type Matcher struct {
	requests       []MatchRequest       // 匹配请求队列，存储所有等待匹配的玩家请求
	mu             sync.Mutex           // 互斥锁，确保对匹配队列的并发操作（添加、移除、匹配）安全
	strategy      MatchmakingStrategy // 当前使用的匹配策略（如随机、技能、地区）
	eventBus       eventbus.EventBus    // 事件总线，用于发布匹配生命周期事件（如开始、取消、成功）
	sessionManager *SessionManager      // 会话管理器引用，用于匹配成功后创建或加入游戏会话
	matchingTicker *time.Ticker         // 匹配定时器，定期触发匹配检查
	stopChan       chan struct{}        // 停止通道，用于优雅关闭匹配循环
}

// NewMatcher 创建匹配器实例，初始化匹配队列和核心依赖
// 参数：
//
//	eventBus: 事件总线实例，用于发布匹配相关事件
//	sessionManager: 会话管理器实例，用于匹配成功后分配会话
//	strategy: 初始匹配策略（如StrategySkillBased）
//
// 返回值：
//
//	*Matcher: 初始化后的匹配器实例
func NewMatcher(eventBus eventbus.EventBus, sessionManager *SessionManager, strategy MatchmakingStrategy) *Matcher {
	return &Matcher{
		requests:       make([]MatchRequest, 0), // 初始化空的匹配请求队列
		strategy:       strategy,                // 设置匹配策略
		eventBus:       eventBus,                // 绑定事件总线
		sessionManager: sessionManager,          // 绑定会话管理器
		stopChan:       make(chan struct{}),     // 初始化停止通道
	}
}

// AddPlayer 将玩家添加到匹配队列，失败返回错误（如玩家已在队列中）
// 参数：
//
//	request: 玩家的匹配请求（包含PlayerID、SkillRating等信息）
//
// 返回值：
//
//	error: 成功返回nil；失败返回错误（如玩家已在队列中）
func (m *Matcher) AddPlayer(request MatchRequest) error {
	m.mu.Lock()		// 加锁，确保添加玩家时的并发安全（避免同时添加同一玩家）
	defer m.mu.Unlock()	// 函数退出时释放锁

	// 检查玩家是否已在匹配队列中（避免重复加入）
	for _, req := range m.requests {
		if req.PlayerID == request.PlayerID {
			return errors.New("player already in matchmaking queue")
		}
	}

	// 记录匹配请求的创建时间（用于后续统计等待时长）
	request.CreateAt = time.Now()
	// 将请求添加到匹配队列
	m.requests = append(m.requests, request)

	// 发布“匹配开始”事件到事件总线，通知其他模块（如前端显示“匹配中”状态）
	m.eventBus.Publish(eventbus.NewBaseEvent(
		eventbus.EventMatchmakingStarted, // 事件名称：匹配开始
		// request.PlayerID,                 // 事件关联的玩家ID
		// "",                               // 冗余参数（无实际意义）
		map[string]interface{}{	// 事件携带的额外数据（技能等级、地区）
			"skill_rating": request.SkillRating,
			"region":	   request.Region,
		},
	))

	log.Infof("Player added to matchmaking: %s", request.PlayerID) // 记录玩家加入队列日志
	return nil
}

// MatchPlayers 执行匹配逻辑，根据当前策略从队列中匹配玩家并创建会话
// 返回值：
//	matchedCount: 成功匹配的玩家数量
//	error: 匹配过程中的错误（如队列玩家不足、会话创建失败）
func (m *Matcher) MatchPlayers() (int, error) {
	m.mu.Lock()
	defer m.mu.Unlock()

	// 获取会话配置（最小玩家数）
	minPlayers := 2 // 实际项目中应从配置文件读取
	if len(m.requests) < minPlayers {
		return 0, errors.New("insufficient players in queue")
	}

	// 根据当前策略匹配玩家（此处实现随机匹配）
	var matchedPlayers []MatchRequest
	switch m.strategy {
	case StrategyRandom:
		// 随机匹配：从队列头部取minPlayers个玩家
		matchedPlayers = m.requests[:minPlayers]
		// 从队列中移除已匹配玩家
		m.requests = m.requests[minPlayers:]
	default:
		return 0, errors.New("unsupported matching strategy")
	}

	// 生成唯一会话ID
	sessionID := uuid.New().String()

	// 创建游戏会话
	sessionConfig := SessionConfig{
		MaxPlayers: minPlayers,
		MinPlayers: minPlayers,
	}
	session, err := m.sessionManager.CreateSessionById(sessionID, sessionConfig)
	if err != nil {
		log.Errorf("Failed to create session: %v", err)
		return 0, err
	}

	// 将匹配玩家加入会话
	for _, req := range matchedPlayers {
		if err := session.AddPlayer(req.PlayerID); err != nil {
			log.Errorf("Failed to add player %s to session: %v", req.PlayerID, err)
			return 0, err
		}
	}

	// 发布匹配成功事件
	m.eventBus.Publish(eventbus.NewBaseEvent(
		// eventbus.EventMessageReceived,
		eventbus.EventMatchmakingSuccess,
		map[string]interface{}{
			"session_id": sessionID,
			"player_ids": extractPlayerIDs(matchedPlayers),
			"strategy":   m.strategy.String(),
		},
	))

	log.Infof("Successfully matched %d players into session %s", len(matchedPlayers), sessionID)
	return len(matchedPlayers), nil
}

// 辅助函数：从匹配请求中提取玩家ID列表
func extractPlayerIDs(requests []MatchRequest) []string {
	ids := make([]string, len(requests))
	for i, req := range requests {
		ids[i] = req.PlayerID
	}
	return ids
}

// StartMatchingLoop 启动匹配循环，定期执行匹配逻辑
// 参数：
//	interval: 匹配检查间隔时间（实际项目中应从配置文件读取）
func (m *Matcher) StartMatchingLoop(interval time.Duration) {
	// 初始化定时器（每interval执行一次匹配检查）
	m.matchingTicker = time.NewTicker(interval)

	// 启动匹配循环协程
	go func() {
		log.Info("Matching loop started")
		m.eventBus.Publish(eventbus.NewBaseEvent(
		eventbus.EventDebug, 
		map[string]interface{}{
			"component": "matcher",
			"message":  "Matching loop started",
			"interval": interval.String(),
		},
	))

		for {
			select {
			case <-m.matchingTicker.C:
				// 定时器触发，执行匹配逻辑
				err := m.matchPlayers()
				if err != nil && err.Error() != "insufficient players in queue" {
					log.Errorf("Matching failed: %v", err)
					m.eventBus.Publish(eventbus.NewBaseEvent(
					eventbus.EventMatchmakingFailed, 
					map[string]interface{}{
						"error": err.Error(),
					},
				))
				}
			case <-m.stopChan:
				// 收到停止信号，退出循环
				log.Info("Matching loop stopping...")
				return
			}
		}
	}()
}

// StopMatchingLoop 停止匹配循环
func (m *Matcher) StopMatchingLoop() {
	if m.matchingTicker != nil {
		m.matchingTicker.Stop() // 停止定时器
	}
	close(m.stopChan) // 关闭停止通道，通知协程退出
	log.Info("Matching loop stopped")
	m.eventBus.Publish(eventbus.NewBaseEvent(
		eventbus.EventDebug, 
		map[string]interface{}{
			"component": "matcher",
			"message":  "Matching loop stopped",
		},
	))
}

// String 将匹配策略转换为字符串（用于日志和事件）
func (s MatchmakingStrategy) String() string {
	switch s {
	case StrategyRandom:
		return "random"
	case StrategySkillBased:
		return "skill_based"
	case StrategyRegionBased:
		return "region_based"
	default:
		return "unknown"
	}
}

// RemovePlayer 将玩家从匹配队列中移除（如玩家主动取消匹配）
// 参数：
//
//	playerID: 要移除的玩家唯一标识
//
// 返回值：
//
//	error: 成功返回nil；玩家不在队列中时返回错误
func (m *Matcher) RemovePlayer(playerID string) error {
	m.mu.Lock()         // 加锁，确保移除玩家时的并发安全
	defer m.mu.Unlock() // 函数退出时释放锁

	// 遍历匹配队列，查找目标玩家
	for i, request := range m.requests {
		if request.PlayerID == playerID {
			// 从队列中移除该玩家（通过切片重组：保留i之前的元素和i之后的元素）
			m.requests = append(m.requests[:i], m.requests[i+1:]...)

			// 发布“匹配取消”事件到事件总线，通知其他模块（如前端更新状态）
			m.eventBus.Publish(eventbus.NewBaseEvent(
				eventbus.EventMatchmakingCancelled, // 事件名称：匹配取消
				// playerID,                           // 事件关联的玩家ID
				// "",                                 // 冗余参数
				nil, // 无额外数据
			))

			log.Infof("Player removed from matchmaking: %s", playerID) // 记录玩家移除日志
			return nil
		}
	}

	return errors.New("player not found in matchmaking queue") // 玩家不在队列中，返回错误
}

// MatchPlayers 执行匹配逻辑：根据策略对队列中的玩家排序，尝试将其分配到游戏会话
// 返回值：
//
//	error: 匹配过程中无严重错误时返回nil（如队列玩家不足时直接返回）
func (m *Matcher) matchPlayers() error {
	m.mu.Lock()         // 加锁，确保匹配过程中队列不会被并发修改
	defer m.mu.Unlock() // 函数退出时释放锁

	// 至少需要2名玩家才能进行匹配（可根据游戏需求调整，如4人游戏需至少4人）
	if len(m.requests) < 2 {
		return nil // 玩家不足，不执行匹配
	}

	// 根据当前匹配策略对玩家队列进行排序（为后续分组做准备）
	switch m.strategy {
	case StrategySkillBased:
		// 按技能等级升序排序（技能接近的玩家会相邻）
		sort.Slice(m.requests, func(i, j int) bool {
			return m.requests[i].SkillRating < m.requests[j].SkillRating
		})
	case StrategyRegionBased:
		// 按地区字符串升序排序（同一地区的玩家会相邻）
		sort.Slice(m.requests, func(i, j int) bool {
			return m.requests[i].Region < m.requests[j].Region
		})
	default: // StrategyRandom（默认随机匹配，此处代码疑似笔误，应为不排序或随机排序）
		// 注：原代码默认分支与RegionBased逻辑相同，实际随机策略应无需排序或打乱顺序
		sort.Slice(m.requests, func(i, j int) bool {
			return m.requests[i].Region < m.requests[j].Region
		})
	}

	// 尝试将玩家分组并加入会话（此处按2人一组处理，可根据会话MaxPlayers调整）
	for i := 0; i < len(m.requests); i += 2 {
		// 避免数组越界（如队列有奇数个玩家时，最后1个不匹配）
		if i+1 >= len(m.requests) {
			break
		}
		player1 := m.requests[i]   // 第1名玩家
		player2 := m.requests[i+1] // 第2名玩家（与player1匹配）

		// 步骤1：查找可用会话（已创建且未满员的会话）
		session, err := m.sessionManager.FindAvailableSession()
		if err != nil {
			// 步骤2：无可用会话时，创建新会话（配置为最大4人、最小2人、自动启动）
			config := SessionConfig{
				MaxPlayers:    4,               // 会话最多4人
				MinPlayers:    2,               // 最少2人即可启动
				Timeout:       5 * time.Minute, // 会话超时时间5分钟
				AutoStart:     true,            // 玩家数达标后自动启动
				Reconnectable: true,            // 允许玩家重连
			}
			session, err = m.sessionManager.CreateSession(config)
			if err != nil {
				log.Errorf("Failed to create session: %v", err)
				continue // 创建会话失败，跳过当前分组
			}
		}

		// 步骤3：将匹配的两名玩家加入会话
		if err := session.AddPlayer(player1.PlayerID); err != nil {
			log.Errorf("Failed to add player %s to session: %v", player1.PlayerID, err)
			continue // 添加玩家1失败，跳过当前分组
		}
		if err := session.AddPlayer(player2.PlayerID); err != nil {
			log.Errorf("Failed to add player %s to session: %v", player2.PlayerID, err)
			// 玩家2添加失败时，需回滚玩家1的添加（避免单边加入）
			session.RemovePlayer(player1.PlayerID)
			continue
		}

		// 步骤4：从匹配队列中移除已成功匹配的玩家
		// 注：原代码此处切片操作有误（i+1后索引会变化），修正逻辑应为：
		// 移除i和i+1位置的元素，剩余元素重组为新切片
		m.requests = append(m.requests[:i], m.requests[i+2:]...)
		// 调整循环索引（因移除了两个元素，下次循环需从当前i开始）
		i -= 2

		log.Infof("Matched players %s and %s in session %s",
			player1.PlayerID, player2.PlayerID, session.ID())
	}

	return nil
}

// StartMatchmaking 启动匹配循环：每隔指定间隔（interval）执行一次匹配逻辑
// 参数：
//
//	interval: 匹配检查的时间间隔（如1*time.Second表示每秒检查一次队列）
func (m *Matcher) StartMatchmaking(interval time.Duration) {
	// 启动独立goroutine运行匹配循环（不阻塞调用者）
	go func() {
		// 创建定时器，每隔interval触发一次匹配检查
		ticker := time.NewTicker(interval)
		defer ticker.Stop() // 确保goroutine退出时停止定时器（避免资源泄漏）

		// 循环监听定时器信号，每次触发时执行匹配
		for range ticker.C {
			err := m.matchPlayers()
			if err != nil {
				log.Errorf("Matchmaking failed: %v", err)
			}
		}
	}()
}

// GetQueueSize 返回当前匹配队列中的玩家数量
// 返回值：
//
//	int: 匹配队列的长度（等待匹配的玩家数）
func (m *Matcher) GetQueueSize() int {
	m.mu.Lock()         // 加锁，确保读取队列长度时的并发安全
	defer m.mu.Unlock() // 函数退出时释放锁
	return len(m.requests)
}
